﻿#pragma once

#include <vector>
#include <map>
#include <boost/signal.hpp>
#include <SFML/Network.hpp>
#include <SFML/System.hpp>
#include "GameClient.h"
#include "PlayerClient.h"
#include "../configuration/NetworkConfiguration.h"


namespace Domain
{
	namespace Network
	{
		/**
		* Klasa serwera gry, podłączająca klientów, wysyłająca i odbierająca od nich komunikaty
		*/
		class GameServer: private sf::Thread
		{
		public:
			/**
			* Typ zdarzenia przyjęcia nowego połączenia
			* @param game_client Klient gry utworzony dla nowego połączenia
			*/
			typedef void OnNewConnection(Domain::Network::GameClient*);
			/**
			* Typ zdarzenia otrzymania danych od klienta gry
			* @param player_client Klient gracza, od którego otrzymano pakiet
			* @param packet Otrzymany pakiet
			*/
			typedef void OnReceiveData(Domain::Network::PlayerClient*, sf::Packet);
			boost::signal<OnNewConnection> newConnectionEvent; /**< Zdarzenie nowego połączenia */

			typedef std::vector<GameClient*> GameClients;
			GameClients game_clients; /**< Klienci gry podłączeni do serwera */

		protected:
			/** Typ przyporzadkowania typów pakietów do sygnałów */
			typedef std::map<Domain::Configuration::Network::PACKET_TYPE, boost::signal<OnReceiveData>*> ReceiveDataEventsMap;
			ReceiveDataEventsMap receive_data_events; /**< Przyporządkowanie typów pakietów do sygnałów */

			sf::SocketTCP Listener; /**< Gniazdo serwera */
			sf::SelectorTCP Selector; /**< Selektor zarządzający wieloma połączeniami od klientów na raz */
			bool is_running; /**< Prawda, jeśli działa wątek oczekujący na wiadomości od klientów */

		public:
			PlayerClient* player_owner; /**< Klient gracza zarządzającego serwerem */

			GameServer();
			~GameServer();
			void serve(); /**< Rozpoczyna pracę serwera, tworzy listener na domyślnym porcie 10337 */
			void stop(); /**< Zatrzymuje pracę serwera */

			/**
			* Wysyła wiadomość do konkretnego gracza
			* @param player_id Identyfikator gracza, do którego ma trafić wiadomość
			* @param packet_type Typ pakietu
			* @param packet Wysyłany pakiet
			*/
			void sendToPlayer(PlayerClient::PlayerId, Domain::Configuration::Network::PACKET_TYPE, sf::Packet);
			/**
			* Wysyła wiadomość do konkretnego gracza
			* @param game_client Klient gry, do którego ma trafić wiadomość
			* @param packet_type Typ pakietu
			* @param packet Wysyłany pakiet
			*/
			void sendToPlayer(GameClient*, Domain::Configuration::Network::PACKET_TYPE, sf::Packet);
			/**
			* Wysyła wiadomość do wszystkich połączonych graczy
			* @param packet_type Typ pakietu
			* @param packet Wysyłany pakiet
			*/
			void sendToAll(Domain::Configuration::Network::PACKET_TYPE, sf::Packet);

			/**
			* Podłącza slot do sygnału wyzwalanego po otrzymaniu od serwera pakietu o podanym typie.
			* Sygnał zostanie wywołany tylko raz (dla każdego slotu), a następnie zostanie zniszczony.
			* @param packet_type Typ pakietu
			* @param slot Funkcja wywoływana po otrzymaniu pakietu
			*/
			boost::signals::connection doOnReceiveData(Domain::Configuration::Network::PACKET_TYPE,
				const boost::signal<OnReceiveData>::slot_function_type&);

			/**
			* Znajduje klienta gracza dla podanego gracza
			* @param player Gracz, którego klient jest szukany
			* @return Znaleziony klient gracza lub NULL
			*/
			GameClient* findClient(Players::Player*);
		protected:
	    	/**
			* Funkcja odziedziczona z sf::Thread, działająca w osobnym wątku.
			* Zawiera pętlę oczekująca na komunikaty od klientów, które przekazuje następnie do funkcji serveSocket(sf::SocketTCP)
			*/
			virtual void Run();

			void deleteSignal(boost::signal<OnReceiveData>* signal);  /**< Wywołuje destruktor dla podanego sygnału */
			/**
			* Podłącza nowego gracza lub odbiera dane od klienta
			* @param socket Gniazdo TCP, z którego nadeszły dane
			*/
			void serveSocket(sf::SocketTCP);
			/**
			* Akceptuje połączenie oczekujące aktualnie w gnieździe serwera
			*/
			void acceptConnection();
			/**
			* Odczytuje dane z gniazda i odbiera pakiet
			* @param socket Gniazdo, z którego mają być odczytane dane
			*/
			void readSocketData(sf::SocketTCP&);
			/**
			* Dodaje do kolejki zdarzeń aplikacji wywołanie sygnału o podanym typie,
			* a następnie wywołanie funkcji usuwającej ten sygnał.
			* @param packet_type Typ pakietu
			* @param game_client Klient gry, dla którego ma być wywołany sygnał
			* @param packet Pakiet
			*/
			void receiveData(Domain::Configuration::Network::PACKET_TYPE, Domain::Network::GameClient*, sf::Packet);
			/**
			* Odbiera pakiet, dekoduje informacje o jego nadawcy i typie, usuwa te informacje,
			* a następnie wywołuje odpowiednie sygnały
			*/
			void packetReceived(sf::Packet&);
			/**
			* @return Identyfikator następnego podłączonego do serwera klienta
			*/
			PlayerClient::PlayerId getNextPlayerId();
		};
	}
}

